#region License

// Copyright 2008-2009 Jeremy Skinner (http://www.jeremyskinner.co.uk)
// 
// Licensed under the Apache License, Version 2.0 (the "License"); 
// you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at 
// 
// http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
// See the License for the specific language governing permissions and 
// limitations under the License.
// 
// The latest version of this file can be found at http://www.codeplex.com/FluentValidation

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using FluentValidation.Results;
using FluentValidation.Validators;

namespace FluentValidation.Internal
{
    /// <summary>
    /// Builds a validation rule and constructs a validator.
    /// </summary>
    /// <typeparam name="T">Type of object being validated</typeparam>
    /// <typeparam name="TProperty">Type of property being validated</typeparam>
    public class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IValidationRuleCollection<T>,
                                             IRuleBuilderInitial<T, TProperty>
    {
        private readonly PropertyModel<T, TProperty> _model;
        private readonly List<IValidationRule<T>> _rules = new List<IValidationRule<T>>();
        private Func<CascadeMode> _cascadeMode = () => ValidatorOptions.CascadeMode;
        private PropertyRule<T, TProperty> _currentRule;

        /// <summary>
        /// Creates a new instance of the <see cref="RuleBuilder{T,TProperty}">RuleBuilder</see> class.
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="expression">Property expression used to initialise the rule builder.</param>
        public RuleBuilder(string identifier, Func<T, TProperty> expression)
        {
            _model = new PropertyModel<T, TProperty>(identifier, expression);
        }

        public CascadeMode CascadeMode
        {
            get { return _cascadeMode(); }
            set { _cascadeMode = () => value; }
        }

        public PropertyModel<T, TProperty> Model
        {
            get { return _model; }
        }

        #region IRuleBuilderInitial<T,TProperty> Members

        public IRuleBuilderInitial<T, TProperty> Configure(Action<RuleBuilder<T, TProperty>> configurator)
        {
            configurator(this);
            return this;
        }

        #endregion

        #region IRuleBuilderOptions<T,TProperty> Members

        /// <summary>
        /// Sets the validator associated with the rule.
        /// </summary>
        /// <param name="validator">The validator to set</param>
        /// <returns></returns>
        public IRuleBuilderOptions<T, TProperty> SetValidator(IPropertyValidator<T, TProperty> validator)
        {
            validator.Guard("Cannot pass a null validator to SetValidator.");
            var rule = new PropertyRule<T, TProperty>(_model, validator);
            _rules.Add(rule);
            _currentRule = rule;
            return this;
        }

        /// <summary>
        /// Sets the validator associated with the rule. Use with complex properties where an IValidator instance is already declared for the property type.
        /// </summary>
        /// <param name="validator">The validator to set</param>
        public void SetValidator(IEnumerable<IValidationRule<TProperty>> validator)
        {
            validator.Guard("Cannot pass a null validator to SetValidator");
            var rule = new ComplexPropertyRule<T, TProperty>(validator, _model);
            _rules.Add(rule);
        }

        public IRuleBuilderOptions<T, TProperty> Configure(Action<ISimplePropertyRule<T, TProperty>> configurator)
        {
            configurator(_currentRule);
            return this;
        }

        #endregion

        #region IValidationRuleCollection<T> Members

        public IEnumerator<IValidationRule<T>> GetEnumerator()
        {
            return _rules.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public IEnumerable<ValidationFailure> Validate(ValidationContext<T> context)
        {
            CascadeMode cascade = _cascadeMode();
            bool hasFailure = false;

            foreach (var rule in _rules)
            {
                IEnumerable<ValidationFailure> results = rule.Validate(context);

                foreach (ValidationFailure result in results)
                {
                    hasFailure = true;
                    yield return result;
                }

                if (cascade == CascadeMode.StopOnFirstFailure)
                {
                    break;
                }
            }

            if (hasFailure)
            {
                _model.OnFailure(context.InstanceToValidate);
            }
        }

        #endregion
    }
}